﻿using System;
using System.Linq;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using iTunesLibrary.Music;

namespace iTunesLibrary.Dal.Xml
{
    public class LibraryParser : ILibraryParser 
    {
        private string _inputPath;
        public LibraryParser(string inputPath):this() 
        {
            _inputPath = inputPath;
        }

        private LibraryParser()
        {
            _readerSettings = new XmlReaderSettings 
            { 
                    IgnoreWhitespace = true,
                    ProhibitDtd = false 
            };
            _writerSettings = new XmlWriterSettings
                {
                    CheckCharacters = true
                };
            _videoRegex = new Regex("video", RegexOptions.IgnoreCase);
        }

        #region Fileds
        private XmlReaderSettings _readerSettings;
        private XmlReader _reader;
        private XmlWriter _writer;
        private XmlWriterSettings _writerSettings;
        private Regex _videoRegex;
        #endregion

        #region IiTunesDataProvider
        public Library Load()
        {
            return LoadReader();
        }
        public Library LoadReader()
        {
            return LoadReader(-1);
        }
        public Library LoadReader(int max)
        {
            Library lib = new Library();
            var count = 0;
            using (Stream stream = File.OpenRead(_inputPath))
            {
                using (_reader = XmlReader.Create(stream, _readerSettings))
                {
                    MoveToTracks();
                    while (_reader.Read())
                    {
                        if (!(_reader.NodeType == XmlNodeType.Element && String.Equals(_reader.Name, "dict")))
                        {
                            continue;
                        }
                        AddTrackReader(lib, _reader.ReadSubtree());
                        count++;
                        if (max > 0 && count == max)
                            return lib;
                    }
                }
            }
            return lib;
        }

        //public Library LoadLinq()
        //{
        //    return LoadLinq(-1);
        //}
        //public Library LoadLinq(int max)
        //{
        //    Library lib = new Library();
        //    var count = 0;
        //    using (Stream stream = File.OpenRead(_inputPath))
        //    {
        //        using (_reader = XmlReader.Create(stream, _readerSettings))
        //        {
        //            var doc =  XDocument.Load(_reader);
        //            var elements = doc.XPathSelectElements("plist/dict/dict/dict");
        //            foreach (var element in elements)
        //            {
        //                AddTrackLinq(lib, element);
        //                count++;
        //                if (max > 0 && count == max)
        //                    return lib;
        //            }
        //        }
        //    }
        //    return lib;
        //}
        
        //private bool Search(Track track, out XElement searched)
        //{
        //    searched = null;
        //    using (Stream stream = File.OpenRead(_inputPath))
        //    {
        //        using (_reader = XmlReader.Create(stream, _readerSettings))
        //        {
        //            XDocument it = XDocument.Load(_reader);
        //            searched = it.XPathSelectElement(string.Format("plist/descendant::dict[(key = 'Track ID' and integer = {0} and string = '{1}')]", track.TrackId.ToString(), track.PersistentID));
                    
        //            if (searched != null)
        //                return true;
        //        }
        //    }
        //    return false;
        //}
        //public bool Update(Track track, string field, string value)
        //{
        //    if (track == null)
        //        return false;
        //    XDocument it;
        //    using (Stream stream = File.Open(_inputPath, FileMode.Open, FileAccess.Read))
        //    {
        //        //load the itunes xml
        //        it = XDocument.Load(stream);
        //    }
        //    //find the key element with value "Track ID" and a following sibling with the right ID
        //    var elements = it.XPathSelectElements("plist/dict/dict/dict/key")
        //        .Where(x => x.Value == "Track ID"
        //        && x.ElementsAfterSelf("integer").FirstOrDefault().Value == track.TrackId.ToString());

        //    var element = elements.FirstOrDefault();
        //    if (element == null)
        //        //the id doesn't exists
        //        return false;

        //    var parent = element.Parent;
        //    var fieldNode = parent.Elements("key").Where(x => x.Value == field).FirstOrDefault();
        //    if (fieldNode == null)
        //        //the field doesn't exists
        //        return false;
        //    var valueNode = fieldNode.ElementsAfterSelf("string").FirstOrDefault();
        //    valueNode.SetValue(value);
        //    using (Stream stream = File.Open(_inputPath, FileMode.Open, FileAccess.Write))
        //    {
        //        //save the itunes xml
        //        it.Save(stream);
        //    }
        //    return true;
        //}
        
        #endregion

        #region tools
        private void MoveToTracks() 
        {
            MoveTo("plist");
            MoveTo("dict");
            MoveTo("dict");
        }
        private void MoveTo(string node) 
        {
            if (!_reader.ReadToDescendant(node)) {
                throw new ParserException("Could not read to descendent: {0}", node);
            }
        }

        /// <summary>
        /// Creates a track by parsing its values from the given reader
        /// </summary>
        /// <param name="reader">The reader to use for parsing the track information. The reader should be placed
        /// at the &lt;dict&gt; node that contains the track information</param>
        /// <returns>The parsed track</returns>
        private void AddTrackReader(Library lib, XmlReader reader)
        {
            XElement document = XElement.Load(reader);
            AddTrackLinq(lib, document);
        }
        private void AddTrackLinq(Library lib, XElement element)
        {
            if (element == null)
            {
                return;
            }

            if (IsTrackTypeIgnored(element))
            {
                return;
            }
            Artist artist = LoadArtist(lib, element);
            Album album = LoadAlbum(lib, element, artist);
            Track track = LoadTrack(lib, element, album, artist);
            if (artist != null && album != null && !lib.Artists[artist].Contains(album))
                lib.Artists[artist].Add(album);
            if (album != null && track != null && !lib.Albums[album].Contains(track))
                lib.Albums[album].Add(track);
            lib.Tracks.Add(track);

        }
        private bool IsTrackTypeIgnored(XContainer root)
        {
            // Ignore podcasts
            if (String.Equals(root.ExtractString("Genre"), "Podcast", StringComparison.CurrentCultureIgnoreCase))
            {
                return true;
            }

            // Ignore videos
            return _videoRegex.IsMatch(root.ExtractString("Kind") ?? String.Empty);
        }
        private Artist LoadArtist(Library lib, XContainer root)
        {
            string artist = root.ExtractString("Album Artist") ?? root.ExtractString("Artist");

            if (artist == null)
            {
                string location = root.ExtractString("Location");
                // If there's no location, this is probably a playlist node
                return null;
            }

            var artistObj = lib.Artists.Keys.SingleOrDefault(existing =>
                existing.Name.Equals(artist, StringComparison.CurrentCultureIgnoreCase));

            if (artistObj == null)
            {
                artistObj = new Artist
                {
                    Name = artist,
                };

                lib.Artists.Add(artistObj, new List<Album>());
            }

            return artistObj;
        }
        private Album LoadAlbum(Library lib, XContainer root, Artist artist)
        {
            string name = root.ExtractString("Album");
            int year = root.ExtractInt("Year");
            int trackCount = root.ExtractInt("Track Count");


            if (artist == null || name == null)
            {
                string location = root.ExtractString("Location");
                if (!String.IsNullOrEmpty(location))
                {
                    Console.Error.WriteLine("Ignoring: {0}", location);
                }

                // If there's no location, this is probably a playlist node
                return null;
            }

            var album = lib.Albums.Keys.SingleOrDefault(existing =>
                existing.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase) &&
                existing.Artist.Equals(artist) &&
                existing.Year.Equals(year) &&
                existing.TrackCount.Equals(trackCount));

            if (album == null)
            {
                album = new Album
                {
                    Artist = artist,
                    Name = name,
                    Year = year,
                    TrackCount = trackCount
                };

                lib.Albums.Add(album, new List<Track>());
            }

            return album;
        }
        private Track LoadTrack(Library lib, XContainer root, Album album, Artist artist)
        {
            var location = System.Web.HttpUtility.UrlDecode(root.ExtractString("Location"));
            return new Track
            {
                TrackId = root.ExtractInt("Track ID"),
                Name = root.ExtractString("Name"),
                Artist = artist,
                Album = album,
                Genre = root.ExtractString("Genre"),
                Kind = root.ExtractString("Kind"),
                Size = root.ExtractInt("Size"),
                TotalTime = root.ExtractInt("Total Time"),
                TrackNumber = root.ExtractInt("Track Number"),
                BitRate = root.ExtractInt("Bit Rate"),
                SampleRate = root.ExtractInt("Sample Rate"),
                Comments = root.ExtractString("Comments"),
                PersistentID = root.ExtractString("Persistent ID"),
                TrackType = root.ExtractString("Track Type"),
                Location = location != null ? location.Replace("file://localhost/", "").Replace("/", "\\") : location,

            };
        }
        #endregion
    }
}
